前面提到設定 resources.limits.cpu 會造成 CPU throttling,造成資源沒有效率的應用,也影響到應用程式的表現,Kubernetes 發現了這個問題,所以提出了一個解決方案:CPU Manager
CPU Manager 的目的是用 cpuset Pod 可以獨佔 CPU,而使用條件有三個
kubelet 設定中 cpu_manager_policy 需為 static
QoS Class = guaranteed
limits.cpu 單位需為整數
剛 cpu_manager_policy 設定為 static 之後,CPU Manager 會維護一個 CPU Pool,讓 KubeReserved
、SystemReserved
、burstableQoS
以及 besteffortQos
共同用這個 CPU Pool,而當有 Guaranteed Pod 且 limits.cpu 單位是整數的時候,就會把 cpuset 設定在上面。
作者建置了一個 GKE,並把其中一個 node pool 的 kubelet 設定 cpu_manager_policy=static,
佈署一個 Nginx Deployment,然後 resources.limits 以及 resources.requests 都設定為 1
這樣的設定就滿足了 cpu_manager_policy=static、QoS Class = guaranteed 以及 limits.cpu 單位是整數的條件,所以可以被獨佔。
我們可以看到 kubelet 的日誌顯示 Pod 被綁定在哪一個 CPU 上面
Oct 08 14:16:03 gke-my-gke-cluster-static-cpu-node-po-3a6376be-d941 kubelet[1704]: I1008 14:16:03.681601 1704 state_mem.go:80] "Updated desired CPUSet" podUID="e93e3ec0-4d04-4795-85ff-6a2c58eaef0e" containerName="fluentbit" cpuSet="0"
Oct 08 14:16:03 gke-my-gke-cluster-static-cpu-node-po-3a6376be-d941 kubelet[1704]: I1008 14:16:03.775045 1704 state_mem.go:80] "Updated desired CPUSet" podUID="e93e3ec0-4d04-4795-85ff-6a2c58eaef0e" containerName="fluentbit-gke" cpuSet="0"
Oct 08 14:16:03 gke-my-gke-cluster-static-cpu-node-po-3a6376be-d941 kubelet[1704]: I1008 14:16:03.929579 1704 state_mem.go:80] "Updated desired CPUSet" podUID="e93e3ec0-4d04-4795-85ff-6a2c58eaef0e" containerName="fluentbit-metrics-collector" cpuSet="0"
Oct 08 14:16:03 gke-my-gke-cluster-static-cpu-node-po-3a6376be-d941 kubelet[1704]: I1008 14:16:03.982960 1704 state_mem.go:80] "Updated desired CPUSet" podUID="94ff5e11-f73c-452d-9179-a91b189e199a" containerName="gke-metrics-agent" cpuSet="0"
Oct 08 14:16:04 gke-my-gke-cluster-static-cpu-node-po-3a6376be-d941 kubelet[1704]: I1008 14:16:04.019762 1704 state_mem.go:80] "Updated desired CPUSet" podUID="94ff5e11-f73c-452d-9179-a91b189e199a" containerName="core-metrics-exporter" cpuSet="0"
Oct 08 14:16:04 gke-my-gke-cluster-static-cpu-node-po-3a6376be-d941 kubelet[1704]: I1008 14:16:04.054535 1704 state_mem.go:80] "Updated desired CPUSet" podUID="94ff5e11-f73c-452d-9179-a91b189e199a" containerName="prometheus-metrics-collector" cpuSet="0"
Oct 08 14:16:04 gke-my-gke-cluster-static-cpu-node-po-3a6376be-d941 kubelet[1704]: I1008 14:16:04.103424 1704 state_mem.go:80] "Updated desired CPUSet" podUID="96a35632-109d-4773-b4ea-1253befc0e36" containerName="csi-driver-registrar" cpuSet="0"
Oct 08 14:16:04 gke-my-gke-cluster-static-cpu-node-po-3a6376be-d941 kubelet[1704]: I1008 14:16:04.153779 1704 state_mem.go:80] "Updated desired CPUSet" podUID="96a35632-109d-4773-b4ea-1253befc0e36" containerName="gce-pd-driver" cpuSet="0"
Oct 08 14:16:04 gke-my-gke-cluster-static-cpu-node-po-3a6376be-d941 kubelet[1704]: I1008 14:16:04.196431 1704 state_mem.go:80] "Updated desired CPUSet" podUID="f5376619-c45d-4f36-9a5e-11e8ac5254d1" containerName="konnectivity-agent" cpuSet="0"
Oct 08 14:16:04 gke-my-gke-cluster-static-cpu-node-po-3a6376be-d941 kubelet[1704]: I1008 14:16:04.232609 1704 state_mem.go:80] "Updated desired CPUSet" podUID="f5376619-c45d-4f36-9a5e-11e8ac5254d1" containerName="konnectivity-agent-metrics-collector" cpuSet="0"
Oct 08 14:16:04 gke-my-gke-cluster-static-cpu-node-po-3a6376be-d941 kubelet[1704]: I1008 14:16:04.285655 1704 state_mem.go:80] "Updated desired CPUSet" podUID="eeec0d059b9283abacfbba2e302d3e00" containerName="kube-proxy" cpuSet="0"
Oct 08 14:16:04 gke-my-gke-cluster-static-cpu-node-po-3a6376be-d941 kubelet[1704]: I1008 14:16:04.331036 1704 state_mem.go:80] "Updated desired CPUSet" podUID="5f7caf44-29f3-4ffc-92f1-fa9cdd409be7" containerName="prometheus" cpuSet="0"
Oct 08 14:16:04 gke-my-gke-cluster-static-cpu-node-po-3a6376be-d941 kubelet[1704]: I1008 14:16:04.386277 1704 state_mem.go:80] "Updated desired CPUSet" podUID="5f7caf44-29f3-4ffc-92f1-fa9cdd409be7" containerName="config-reloader" cpuSet="0"
Oct 08 14:16:07 gke-my-gke-cluster-static-cpu-node-po-3a6376be-d941 kubelet[1704]: I1008 14:16:07.604113 1704 state_mem.go:80] "Updated desired CPUSet" podUID="17838689-d6c7-4dd0-a084-cba58fdc9759" containerName="nginx" cpuSet="1"
我們可以很明顯的觀察到,除了 nginx 之外都被分配到 cpuSet=0
Oct 08 14:16:07 gke-my-gke-cluster-static-cpu-node-po-3a6376be-d941 kubelet[1704]: I1008 14:16:07.604113 1704 state_mem.go:80] "Updated desired CPUSet" podUID="17838689-d6c7-4dd0-a084-cba58fdc9759" containerName="nginx" cpuSet="1"
而日誌顯示 nginx 被綁定到 cpu 1
接下來我們從 cgroup 可以看到 cpuset 被綁定到 cpu 1
cat /sys/fs/cgroup/kubepods.slice/kubepods-pod17838689_d6c7_4dd0_a084_cba58fdc9759.slice/cri-containerd-e69cb15c5c8c402574f0b96d866ee53ffe36175ca18f4a476f6d7b8453be7c63.scope/cpuset.cpus
1
為了避免 noisy neighbours problem,CPU Manager 可以設定讓 Pod 獨佔物理 CPU 而不是 vCPU,這個功能從 full-pcpus-only 可以開啟。
除此之外還有 NUMA 相關的設定,不過作者目前還沒有機會碰到,所以就不多做介紹了。
REF:
https://www.hwchiu.com/docs/2023/container-vm#monitor
目前我看到寫 cgroup 最詳細的文章,裡面關於 CPU Throttling 的圖解相當清楚。
https://medium.com/geekculture/layer-by-layer-cgroup-in-kubernetes-c4e26bda676c
關於 Layered cgroup 的介紹
https://github.com/kubernetes/design-proposals-archive/blob/main/node/node-allocatable.md
關於 Layered cgroup 的 KEP
https://www.youtube.com/watch?v=x9_9iaVszpM&themeRefresh=1
從對於 resources.limits 以及 resources.requests 的誤解這樣的方式敘事,是看到這個影片的靈感
https://kubernetes.io/docs/tasks/administer-cluster/cpu-management-policies/
Static CPU 的文件